因为正则表达式的在文本处理,字符串匹配中有着重要应用,因此本文对其基本语法规则及在Python中的应用进行了简要介绍。
注:本文大部分是对博文正则表达式30分钟入门教程,LiZeC的正则表达式笔记以及python re库官方文档的归纳和整理,更详细内容可查阅原文。
基本语法
- 基本概念: 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来匹配、检测、替换特定的字符串等。
字符
- 普通字符:通常正则表达式中出现的任意一个字符代表匹配和他们一样的字符。
- 特殊字符:特殊字符并不匹配他们本身,而是有特殊含义的,具体有:
. ^ $ * + ? { } [ ] \ | ( )
若要匹配这些字符本身,则需要加上\
进行转义(escape),如\*
表示匹配*
本身。
特殊字符含义
字符 | 含义 |
---|---|
. | 匹配任意单个字符(除换行符“\n”外) |
\w | 匹配任意字母、数字、下划线、汉字(即匹配普通字符) |
\s | 匹配任意的空白符(空格、制表符(Tab)、换行符、中文全角空格等) |
\d | 匹配数字(0-9) |
\b | 匹配单词的开始或结束(单词边界),即非字母数字的任意其他字符 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
对单词边界
\b
:如正则表达式\blove\b
,就会匹配句子”I love you”中的love, 而不会匹配”I aloveb you”中的love。一些字符的反义表示
如\w
,\b
这些特殊字符的大写表示相反的含义,具体如下:
字符 | 含义 |
---|---|
\W | 匹配不是字母、数字、下划线、汉字的字符(多用来匹配特殊字符) |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
重复匹配限定符
以下字符称为“限定符”(限制次数的符号),跟在特殊字符的后面,表示重复该字符特定次数。
如\d+
匹配1个或更多个的数字。
限定符 | 含义 |
---|---|
* | 重复\(\geq\)0次 |
+ | 重复\(\geq\)1次 |
? | 重复0次或1次 |
{n} | 重复n次 |
{n,} | 重复\(\geq\)n次 |
{n,m} | 重复n到m次 |
贪婪、懒惰匹配原则
- 正则表达式的匹配原则为贪婪匹配:即在满足条件的情况下,匹配尽可能多的字符。
eg.a.*b
会匹配以a开始,以b结束的最长的字符串。如果用来搜索aabab
,则会匹配整个字符串aabab
而非aab
。 - 若需要懒惰匹配:即匹配尽可能少的字符,就在相应的重复字符后加
?
即可。
eg.a.*?b
匹配以a开始,以b结束的最短的字符串,搜索aabab
会匹配到aab
和ab
两个子串(之所以没有仅仅匹配到ab
子串,是因为正则表达式的匹配还会考虑开始的先后顺序,最开始匹配的优先级最高)。 - 懒惰匹配限定符如下:
限定符 | 含义 |
---|---|
*? | 重复\(\geq\)0次,但尽可能少重复 |
+? | 重复\(\geq\)1次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,}? | 重复\(\geq\)n次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
字符类
- 在
[
和]
中的若干字符构成一个字符类(character class)。 - 字符类是为了匹配某种字符集合,表示此位置可以匹配这个类中的任意一个字符。
- 整个字符类所起到的作用和普通字符相同,都是只匹配单个字符,因此字符类可作为整体再接受其他限定,如
? + {n,m}
等等。 - 可以使用
-
来表示一个范围,例如[a-c]
表示[abc]
- 在字符类中的特殊符号不被转义
- 反向匹配:在字符类中,如果以^开头,则表示匹配除此字符类中提及的任何其他字符。如
[^5]
匹配任何不是5的字符。
分枝条件
- 在正则表达式表示“或”的逻辑,两个条件用
|
连接即可。
eg.\d{5}-\d{4}|\d{5}
这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。 - 在使用分枝条件时要注意各个条件的顺序,如果上文改成
\d{5}|\d{5}-\d{4}
的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。因为一旦前面的分枝满足的话就不会再管其他条件了。
注释
不推荐在正则表达式内部写注视,注释推荐写在正则表达式的外部语言中。
正则表达式高级特性
分组
- 上文提到了如何重复单个字符(直接在字符后面加表示重复的限定符即可),但若想重复多个字符,就需要用到分组的概念。
- 用小括号
()
来指定分组(也叫子表达式),之后就可以对这个分组的整体进行重复或其他操作。
eg.(\d{1,3}\.){3}\d{1,3}
,就是一个比较粗糙的IP地址匹配式。
后向引用
- 使用小括号指定一个分组后,这个分组可以作为一个整体在后文中作进一步处理。
- 默认情况下,每个组会有一个组号,从左到右,第一个出现的分组组号为1(注意不是0),第二个为2,以此类推。
- 如何引用:在后问中使用
\
+组号的形式来引用,如\1
表示引用1号分组的。
eg.\b(\w+)\b\s+\1\b
可以来匹配如go go 或kitty kitty 这种连着两个重复单词。
零宽断言
- 用于查找在某些内容前面或后面的东西(但不包括这些内容),类似于
^ $ \b
这种占位符,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此被称为零宽断言。 (?=exp)
:用于匹配exp前面出现的表达式。
e.g\b\w+(?=ing\b)
用于匹配以ing为结尾的单词的前面部分(不包括ing)(?<=exp)
:用于匹配exp后面出现的表达式。
e.g(?<=\bre)\w+\b
用于匹配以re开头的单词的后面部分(不包括re)- 更多示例:
(?<=\s)\d+(?=\s)
:匹配以空白符间隔的数字(不包括这些空白符)((?<=\d)\d{3})+\b
:要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分(用它对1234567890
进行查找时结果是234567890
)
负向零宽断言
- 用于确保某个模式不会出现。与字符类
[^exp]
的区别:虽然不匹配这个字符,但字符类总是会匹配某个字符的,这会限制字符类的应用场景;而负向零宽断言不匹配字符,其只指代一个位置。
eg. 如果用\b\w*q[^u]\w*\b
来匹配“出现了字母q,但是q后面跟的不是字母u”的单词,则像“Iraq, Benq”这种q直接作为最后一个字符的情况就会出错(字符类总要匹配一个字符),因此就需要负向零宽断言。 (?!exp)
:断言此位置的后面不能匹配表达式exp。
eg.\b((?!abc)\w)+\b
匹配不包含连续字符串abc的单词(?<!exp)
:断言此位置的前面不能匹配表达式exp。
eg.(?<![a-z])\d{7}
匹配前面不是小写字母的七位数字。
递归匹配
- 用于匹配一些嵌套的层次结构,如
(100*(50+15))
,如果只是简单地使用\(.+\)
则只会匹配到最左边的左括号和最右边的右括号之间的内容。假如原来的字符串里的左括号和右括号出现的次数不相等,比如(5/(3+2)))
,那我们的匹配结果里两者的个数也不会相等。如果想要想匹配到最长的,而且配对正确的字符串,就需要用到递归匹配。 - 具体内容参见博文正则表达式30分钟入门教程
经典正则表达式实例
正则表达式 | 解释 |
---|---|
^[A-Za-z]+$ |
由26个字母组成的字符串 |
^[A-Za-z0-9]+$ |
由26个字母和数字组成的字符串 |
^-?\d+$ |
整数形式的字符串 |
^[0-9]*[1-9][0-9]*$ |
正整数形式的字符串 |
[\u4e00-\u9fa5] |
判断是不是中文字符 |
在Python中使用正则表达式
使用原生字符串
- 正则表达式中使用
\n
表示转义,而python中恰好也使用\n
表示转义,因此在python中使用正则表达式则需要\\
来表示反斜杠。 - 为了节省过多的反斜杠,可以使用Python原生字符串特性,即在字符串开头加上
r
,如r\d
,在这个字符串中每个字符表示其本身,Python不进行转义。
re库
在Python中使用正则表达式直接导入re
库即可 import re
。
常用函数介绍
函数 | 功能 |
---|---|
re.compile() | 编译正则表达式,返回Regular Expression Objects对象 |
re.search() | 在字符串中查找匹配的子串第一次出现的位置,匹配成功返回match对象否则返回None |
re.findall() | 在字符串中查找所有满足条件的子串,返回string list |
re.match() | 强制从起始位置开始匹配,匹配成功返回match对象否则返回 None |
re.split() | 将正则表达式作为separator来分割字符串,返回string list |
re.sub() | 在字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
re.escape() | 自动在字符串中添加 “\” 转义符(除去字母数字下划线),返回修改后的字符串 可用来自动生成包含特殊字符的正则表达式 |
参数说明:
- re.compile(pattern, flags=0)
pattern: 表示正则表达式的字符串
flags: 正则表达式的控制标记 - re.search(pattern, string, flags=0)
pattern:同上
string:待查找的字符串
flags: 同上 - re.findall(pattern, string, flags=0)
参数同上 - re.match(pattern, string, flags=0)
参数同上
re.fullmatch(pattern, string, flags=0)
参数同上,只不过是查找整个字符串 - re.sub(pattern, repl, string, count=0, flags=0)
repl: 用来替代匹配到的子串的字符串 - re.escape(pattern)
eg.pattern1 = re.escape('python.exe'))
,pattern1就为”python\.exe”,可直接作为pattern参数传入其他函数中,用来匹配“python.exe”。当pattern含有大量特殊字符时使用这个函数就很方便。
str.replace()和re.sub的比较
- str.replace(old, new[, count])是字符串的替代函数,其中new为替换的字符串,old为待替换的字符串,old只能为substring而不能为字符串,因此替换功能较为简单。
- re.sub(pattern, repl, string, count=0, flags=0)则使用了正则表达式,可进行更复杂的替换操作,但同时开销也更大。
- 因此能用replace()尽量用,复杂的替换操作再使用正则表达式。
控制标记flag介绍
标记名(简写/全称) | 含义 |
---|---|
re.A / re.ASCII | 使\w,\b,\s和\d只匹配ASCII字符 eg. 不会匹配汉字和其他Unicode字符 |
re.I / re.IGNORECASE | 忽略正则表达式大小写 |
re.M / re.MULTILINE | 使得^表示每一行的开始,$表示每一行的结束 原义仅表示一个单词的开始和结束 |
re.S / re.DOTALL | 使得.可以匹配\n字符 |
re.X / re.VERBOSE | 忽略正则表达式内部的空白符 |
注:这些变量在VSCode的python下没有提示,但可以运行。
正则表达式的两种使用方法
直接函数调用:result = re.search(pattern, string)
先编译后使用:
prog = re.compile(pattern)
result = prog.search(string)两种方式效果相同,且因为缓存机制,在正则表达式数量较少的的情况下,两者效率也相近;但如果重复地调用很多正则表达式,先编译好的效率会更高。
Match类
re.search()和re.match()两个函数的返回对象,包含了匹配的相关信息,常用函数和属性如下:
名称 | 说明 |
---|---|
match.group() | 返回特定的分组,string或tuple的形式 |
match.groups() | 以tuple的形式返回所有分组 |
match.start() | 返回特定分组的起始地址 |
match.end() | 返回特定分组的结束地址 (会比实际大1位,因为python字符串截取前闭后开的特性) |
match.re | 为编译好的regular expression object对象 |
match.string | 为传入到re.search()或re.match()的待匹配字符串 |
参数说明:
- match.group([group1, …])
group1为组号,默认为0(这时返回the whole match);当没有参数或只有一个参数时返回string,当有2个或以上参数时返回tuple。 - match.groups(default=None)
default为没有匹配到的分组指定所显示的名字,通常保持默认即可。 - match.start([group])、match.end([group])
两个函数中group都是指组号,返回特定组号的起始、终止地址;
因此相应分组的子串可通过m.string[m.start(g):m.end(g)]
来获取。
Post Date: 2019-02-07
版权声明: 本文为原创文章,转载请注明出处